#include <iostream>
#include <sstream>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
#include "ConfigDbReader.h"
#include "Config.h"
#include "ConfigError.h"
#include "../db/TableEnums.h"
#include "../logs/Logger.h"
#include "../filter/Rule.h"
#include "../filter/Condition.h"

//#define DEBUG
#define foreach BOOST_FOREACH

using namespace kc_sf;

// --------------------------------------------------- //

void ConfigDbReader::operator()()
{
	readConfigurationOptions();
	readRules();
}

// --------------------------------------------------- //

void ConfigDbReader::readConfigurationOptions()
{
	DatabaseTable conf_table = conn.readTable( "configuration_options" );
	DatabaseRecord db_options = conf_table.getRecord( 0 );

	Config &cfg = Config::getInstance();

	cfg.cCheckAntivir = boost::lexical_cast <bool>( db_options[ AllowAntivir ] );
	cfg.cListenPort = boost::lexical_cast <int>( db_options[ ListenPort ] );
	cfg.cLogsFilepath = db_options[ LogsFilepath ];
	cfg.cLogsLevel = ( LogLevel )boost::lexical_cast <int>( db_options[ LogsLevel ] );
	cfg.cMaxMessageSize = boost::lexical_cast <unsigned long>( db_options[ MsgMaxSize ] );
	cfg.cMaxSessions = boost::lexical_cast <int>( db_options[ Sessions ] );
	cfg.cMode = boost::lexical_cast <bool>( db_options[ AllowTmpMsgs ] ) ? Safe : Fast;
	cfg.cMyDomain = db_options[ Domain ];
	cfg.cSmtpPort = boost::lexical_cast <int>( db_options[ ServerPort ] );
	cfg.cSmtpServer = db_options[ ServerAddress ];
	cfg.cSslListenPort = boost::lexical_cast <int>( db_options[ SslListenPort ] );

	std::string address = db_options[ RedirectAddress ];
	std::vector < std::string > aparts;
	boost::split( aparts, address, boost::is_any_of( "@" ) );
	cfg.cRedirectAddress.aLocalpart = aparts[ 0 ];
	cfg.cRedirectAddress.aDomain = aparts[ 1 ];
}

// --------------------------------------------------- //

void ConfigDbReader::readRules()
{
	Config::getInstance().cRules.clear();

	enum
	{
		RuleId = 0,
		Action,
		Priority,
		Expression,
		CondId,
		CondType
	};

	std::stringstream rule_conditions_query;
	rule_conditions_query << "select rule.id, rule.action, rule.priority, rule.expression, cond.id as cond_id, cond.ctype\n"
		<< "from filtering_rule as rule\n"
		<< "join filtering_rule_conditions as conditions\n"
		<< "on (rule.id = conditions.rule_id)\n"
		<< "join filtering_condition as cond\n"
		<< "on (conditions.condition_id = cond.id)\n"
		<< "where rule.is_tmp <> 1;";

	DatabaseTable db_rcond_table = conn.query( rule_conditions_query.str() );

	Rule *last_rule = NULL;
	std::string last_rule_id = "0";

	foreach( DatabaseRecord db_rule_condition, db_rcond_table.selectAll() )
	{
		// ta sama regula co poprzednio
		if( last_rule_id == db_rule_condition[ RuleId ] )
		{
			Logic::Symbol *condition = readCondition(
					db_rule_condition[ CondId ],
					db_rule_condition[ CondType ] );
			last_rule->addCondition( condition );
			delete condition;
			continue;
		}

		if( last_rule != NULL )
		{
			last_rule->buildRpnExpression();
			Config::getInstance().cRules.push_back( *last_rule );
			delete last_rule;
			last_rule = NULL;
		}

		// nowa regula
		last_rule_id = db_rule_condition[ RuleId ];
		ActionType action = ( ActionType )boost::lexical_cast <int>( db_rule_condition[ Action ] );
		int priority = boost::lexical_cast <int>( db_rule_condition[ Priority ] );
		std::string expression = db_rule_condition[ Expression ];
		checkExpression( expression );

#ifdef DEBUG
		std::cout << "Rule " << Config::getInstance().cRules.size() << " " << ActionTypeToString( action ) << std::endl;
#endif // DEBUG

		Logic::Symbol *condition = readCondition(
				db_rule_condition[ CondId ],
				db_rule_condition[ CondType ] );

		last_rule = new Rule( expression, action, priority );
		last_rule->addCondition( condition );
		delete condition;
	}

	// ostatnia regula
	if( last_rule != NULL )
	{
		last_rule->buildRpnExpression();
		Config::getInstance().cRules.push_back( *last_rule );
		delete last_rule;
		last_rule = NULL;
	}
}

// --------------------------------------------------- //

Logic::Symbol * ConfigDbReader::readCondition(
		const std::string &id_str,
		const std::string &type_str ) const
{
	ConditionType ctype = ( ConditionType )boost::lexical_cast <int>( type_str );
	DatabaseRecord values = readConditionValues( ctype, id_str );

	return
		ctype == AnrCondition ? createAnrCondition( values )
		: ctype == AttachmentCondition ? createAttachmentCondition( values )
		: ctype == DateCondition ? createDateCondition( values )
		: ctype == FromCondition ? createFromCondition( values )
		: ctype == KeywordCondition ? createKeywordCondition( values )
		: ctype == ResentCondition ? createResentCondition( values )
		: ctype == SizeCondition ? createSizeCondition( values )
		: ctype == SubjectCondition ? createKeywordCondition( values )
		: ctype == ToCondition ? createToCondition( values )
		: NULL;
}

// --------------------------------------------------- //

Logic::Symbol * ConfigDbReader::createAnrCondition( const DatabaseRecord &values ) const
{
	enum { ConditionId = 0, AnrOperator, Anr };

	Operator op = ( Operator )boost::lexical_cast <int>( values[ AnrOperator ] );
	int anr = boost::lexical_cast <int>( values[Anr] );

#ifdef DEBUG
	std::cout << "createAnrCondition: " << values[ AnrOperator ] << " "<< values[Anr] << std::endl;
#endif // DEBUG

	return new MsgANrCond( op, anr );
}

// --------------------------------------------------- //

Logic::Symbol * ConfigDbReader::createAttachmentCondition( const DatabaseRecord &values ) const
{
	enum
	{
		ConditionId = 0,
		SizeOperator,
		Size,
		ContentType,
		ContentSubtype,
		NameOperator,
		Name,
		ExtensionOperator,
		Extension,
		HowManyOperator,
		HowMany
	};

	std::string
		size_op_str = values[ SizeOperator ],
		name_op_str = values[ NameOperator ],
		extension_op_str = values[ ExtensionOperator ],
		content_type_str = values[ ContentType ];

	AttchCond *attch_cond = new AttchCond();
	if( !boost::iequals( "NULL", size_op_str ) )
	{
		Operator op = ( Operator )boost::lexical_cast <int>( size_op_str );
		unsigned long size = boost::lexical_cast<unsigned long>( values[ Size ] );
		attch_cond->aSizeCondition = new AttchSizeCond( op, size * 1000 );

#ifdef DEBUG
		std::cout << "ASizeCondition: " << values[ SizeOperator ] << values[Size] << std::endl;
#endif // DEBUG
	}

	if( !boost::iequals( "NULL", name_op_str ) )
	{
		Operator op = ( Operator )boost::lexical_cast<int>( name_op_str );
		attch_cond->aFilenameCondition = new AttchFilenameCond( op, values[ Name ] );

#ifdef DEBUG
		std::cout << "aFilenameCondition: " << values[ NameOperator ] << values[Name] << std::endl;
#endif // DEBUG
	}

	if( !boost::iequals( "NULL", extension_op_str ) )
	{
		Operator op = ( Operator )boost::lexical_cast<int>( extension_op_str );
		attch_cond->aExtensionCondition = new AttchFilenameCond( op, values[ Extension ], true );

#ifdef DEBUG
		std::cout << "aExtensionCondition: " << values[ ExtensionOperator ] << " " << values[Extension] << std::endl;
#endif // DEBUG
	}

	if( !boost::iequals( "NULL", content_type_str ) && !content_type_str.empty() )
	{
		attch_cond->aContentTypeCondition = new AttchCTypeCond( content_type_str, values[ ContentSubtype ] );
#ifdef DEBUG
		std::cout << "aContentTypeCondition: " << values[ ContentType ] << " " << values[ContentSubtype] << std::endl;
#endif // DEBUG
	}

	attch_cond->hmHowMany = boost::lexical_cast <int>( values[ HowMany ] );
	attch_cond->hmOperator = ( Operator )boost::lexical_cast <int>( values[ HowManyOperator ] );
#ifdef DEBUG
	std::cout << "hmHowMany: " << values[ HowManyOperator ] << " "<< values[HowMany] << std::endl;
#endif // DEBUG

	return attch_cond;
}

// --------------------------------------------------- //

Logic::Symbol * ConfigDbReader::createDateCondition( const DatabaseRecord &values ) const
{
	enum { ConditionId = 0, From, To };

	boost::posix_time::ptime from( boost::posix_time::time_from_string( values[ From ] ) );

#ifdef DEBUG
	std::cout << "createDateCondition: " << values[ From ] << "  "<< values[ To ] << std::endl;
#endif // DEBUG

	if( boost::iequals( values[ To ], "NULL" ) )
		return new DateCond( from );

	boost::posix_time::ptime to( boost::posix_time::time_from_string( values[ To ] ) );
	return new DateCond( from, to );
}

// --------------------------------------------------- //

Logic::Symbol * ConfigDbReader::createFromCondition( const DatabaseRecord &values ) const
{
	enum Column
	{
		ConditionId = 0,
		Localpart,
		Domain,
		AddressType
	};

#ifdef DEBUG
	std::cout << "createFromCondition: " << values[ Localpart ] << "  "<< values[ Domain ] << std::endl;
#endif // DEBUG

	return new FromCond( values[ Localpart ], values[ Domain ] );
}

// --------------------------------------------------- //

Logic::Symbol * ConfigDbReader::createKeywordCondition( const DatabaseRecord &values ) const
{
	enum
	{
		ConditionId = 0,
		KeywordOperator,
		Keyword,
		KeywordRangeColumn,
		HowManyOperator,
		HowMany
	};

	Operator op = ( Operator )boost::lexical_cast <int>( values[ KeywordOperator ] );
	KeywordRange range = ( KeywordRange )boost::lexical_cast <int>( values[ KeywordRangeColumn ] );

	KeywordCond *keyword_cond = new KeywordCond( op, values[ Keyword ], range );
	keyword_cond->hmHowMany = boost::lexical_cast<int>( values[ HowMany ] );
	keyword_cond->hmOperator = ( Operator )boost::lexical_cast<int>( values[ HowManyOperator ] );

#ifdef DEBUG
	std::cout << "createKeywordCondition: " << values[ KeywordOperator ] << "  "<< values[ KeywordRangeColumn ] << " " << values[Keyword] << std::endl;
	std::cout << "hmHowMany: " << values[ HowManyOperator ] << " "<< values[ HowMany ] << std::endl;
#endif // DEBUG

	return keyword_cond;
}

// --------------------------------------------------- //

Logic::Symbol * ConfigDbReader::createResentCondition( const DatabaseRecord &values ) const
{
	enum { ConditionId = 0, Resent };

#ifdef DEBUG
	std::cout << "createResentCondition: " << values[ Resent ] << "  " << std::endl;
#endif // DEBUG
	return new MsgResentCond( boost::lexical_cast <bool>( values[ Resent ] ) );
}

// --------------------------------------------------- //

Logic::Symbol * ConfigDbReader::createSizeCondition( const DatabaseRecord &values ) const
{
	enum { ConditionId = 0, SizeOperator, Size };

#ifdef DEBUG
	std::cout << "createSizeCondition: " << values[ SizeOperator ] << "  " << values[ Size ] << std::endl;
#endif // DEBUG

	int size = boost::lexical_cast<unsigned long>( values[ Size ] );
	Operator op = ( Operator )boost::lexical_cast<int>( values[ SizeOperator ] );

	return new MsgSizeCond( size * 1000, op );
}

// --------------------------------------------------- //

Logic::Symbol * ConfigDbReader::createToCondition( const DatabaseRecord &values ) const
{
	enum Column
	{
		ConditionId = 0,
		Localpart,
		Domain,
		AddressType
	};

#ifdef DEBUG
	std::cout << "createToCondition: " << values[ Localpart ] << "  " << values[ Domain ] << std::endl;
#endif // DEBUG

	return new ToCond( values[ Localpart ], values[ Domain ] );
}

// --------------------------------------------------- //

DatabaseRecord ConfigDbReader::readConditionValues(
	ConditionType ctype,
	const std::string &id_str ) const
{
	FilterTableType table_type =
		ctype == AnrCondition ? FilteringANrCondition
		: ctype == AttachmentCondition ? FilteringAttachmentCondition
		: ctype == DateCondition ? FilteringDateCondition
		: ctype == FromCondition ? FilteringAddressCondition
		: ctype == KeywordCondition ? FilteringKeywordCondition
		: ctype == ResentCondition ? FilteringResentCondition
		: ctype == SizeCondition ? FilteringSizeCondition
		: ctype == SubjectCondition ? FilteringKeywordCondition
		: ctype == ToCondition ? FilteringAddressCondition
		: UnknownFilteringTable;

	if( table_type == UnknownFilteringTable )
		throw ConfigError( "Podano nieznany typ warunku przy odczycie tabeli warunku z bazy danych: " + ctype );

	std::string table_name = FilterTableTypeToString.at( table_type );
	std::string query = "select * from " + table_name +
			+ " where condition_id = " + id_str + ";";

	DatabaseTable condition_table = conn.query( query ); // 1 rekord
	if( condition_table.recordsNr() == 0 )
		throw ConfigError( "W tabelce " + table_name + " nie ma żadnych wartosci! Warunek " + id_str );

	return condition_table.getRecord( 0 );
}

void ConfigDbReader::checkExpression( const std::string &expr ) const
{
	int brackets_nr = 0;

	for( size_t i = 0; i < expr.size(); ++i )
	{
		if( expr[ i ] == ' ' || ( expr[ i ] >= '0' && expr[ i ] <= '9' ) )
			continue;
		else
		{
			switch( expr[ i ] )
			{
				case '|':
				case '&':
					if( i == expr.size() - 1 || expr[ i+1 ] == ')' )
						throw ConfigError( "Błąd w wyrażeniu regularnym. Wyrażenie albo podwyrażenie w nawiasie nie może kończyć się znakiem " + expr[ i ] );

					if( i == 0 || expr[ i-1 ] == '(' )
						throw ConfigError( "Błąd w wyrażeniu regularnym. Wyrażenie albo podwyrażenie w nawiasie nie może rozpoczynać się od znaku " + expr[ i ] );

					break;
				case '~':
					if( i == expr.size() - 1 || expr[ i+1 ] == ')' )
						throw ConfigError( "Błąd w wyrażeniu regularnym. Wyrażenie albo podwyrażenie w nawiasie nie może kończyć się znakiem " + expr[ i ] );

					break;
				case '(':
					++ brackets_nr;
					break;
				case ')':
					-- brackets_nr;
					break;
				default:
					break;
			}
		}
	}

	if( brackets_nr != 0 )
		throw ConfigError( "Błąd w wyrażeniu regularnym. Nieprawidłowa ilość nawiasów" );
}
